[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/reader search results #4096

Merged
merged 54 commits into from
May 23, 2016

Conversation

nbradbury
Copy link
Contributor

Expands upon #4085 by querying the backend for search results and showing them within the main reader list (ie: within the "masterbar" reader). Changes include:

  • Uses the new read/search endpoint to search for posts
  • Adds ReaderSearchService to perform the search
  • Supports infinite scroll of search results
  • Search suggestions can now be individually removed
  • Search results are cached during the current session, and purged the next time the app is started

This PR turned out to be considerably larger than I anticipated, so if any non-trivial issues are found I may move them to a separate PR to avoid inflating this one further.

Note that the design & wording still aren't final - a design review will occur at a later date, and this can be merged into the feature branch prior to that review.

0a72e9f4-1b79-11e6-8426-ca44a164867f

Update: I meant to mention that I've seen some weird behavior with SearchView on Android emulators, so I recommend using a real device to test this.

@mzorz mzorz self-assigned this May 18, 2016
@mzorz
Copy link
Contributor
mzorz commented May 20, 2016

Hi @nbradbury ! I’ve been playing around with it - it’s a great tool, even addictive at times :) in any case it certainly is a long-awaited feature, so I decided to first give it a good amount of smoke testing from the user point of view before getting to the code itself. Here’s my initial feedback:

  • while I took note that the design isn’t final, just a minor thing I’m sure the design board will mention is the fonts, left arrow and cross in the search box being black - they should be “white” like the filter. The blinking cursor is white, which is OK

  • also, I took note of this:

    Update: I meant to mention that I've seen some weird behavior with SearchView on Android emulators, so I recommend using a real device to test this.

Nevertheless I went ahead and did test not only on the phone but also on genymotion with a 5.1 image and found some things worth mentioning because of the probable impact (force close level) these might have (if this is exactly what you were referring to with “weird behavior” then just skip these please):

  • When having entered a search phrase, then clicking backspace repeatedly like crazy I managed to get either of these two exceptions (with force close) several times:

Exception #1 android.database.StaleDataException:

05-20 17:52:22.511 2962-2962/org.wordpress.android E/AndroidRuntime: FATAL EXCEPTION: main
                                                                     Process: org.wordpress.android, PID: 2962
                                                                     android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
                                                                         at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:139)
                                                                         at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:74)
                                                                         at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:226)
                                                                         at android.widget.AdapterView.rememberSyncState(AdapterView.java:1226)
                                                                         at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:820)
                                                                         at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6140)
                                                                         at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
                                                                         at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
                                                                         at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347)
                                                                         at android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315)
                                                                         at android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68)
                                                                         at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
                                                                         at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                         at android.os.Looper.loop(Looper.java:135)
                                                                         at android.app.ActivityThread.main(ActivityThread.java:5254)
                                                                         at java.lang.reflect.Method.invoke(Native Method)
                                                                         at java.lang.reflect.Method.invoke(Method.java:372)
                                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
                                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
05-20 17:52:26.568 2962-2962/? I/Process: Sending signal. PID: 2962 SIG: 9

Exception # 2: java.lang.IllegalStateException:

05-20 18:34:14.844 5637-5637/org.wordpress.android E/AndroidRuntime: FATAL EXCEPTION: main
                                                                     Process: org.wordpress.android, PID: 5637
                                                                     java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteQuery: SELECT * FROM tbl_search_suggestions WHERE query_string LIKE ? ORDER BY date_used DESC LIMIT 5
                                                                         at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55)
                                                                         at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:58)
                                                                         at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:152)
                                                                         at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:124)
                                                                         at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:214)
                                                                         at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225)
                                                                         at android.widget.AdapterView.rememberSyncState(AdapterView.java:1226)
                                                                         at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:820)
                                                                         at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6140)
                                                                         at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
                                                                         at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
                                                                         at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347)
                                                                         at android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315)
                                                                         at android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68)
                                                                         at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
                                                                         at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                         at android.os.Looper.loop(Looper.java:135)
                                                                         at android.app.ActivityThread.main(ActivityThread.java:5254)
                                                                         at java.lang.reflect.Method.invoke(Native Method)
                                                                         at java.lang.reflect.Method.invoke(Method.java:372)
                                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
                                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
05-20 18:34:14.849 1330-1889/system_process W/ActivityManager:   Force finishing activity 1 org.wordpress.android/.ui.main.WPMainActivity
  • This next one is particular to the emulator and might probably not be relevant to real users, but here it is: when I’m on the Reader tab, with the search box focused and the cursor blinking and the history matching dropdown list open, then tapped the home button, changed Settings -> Language & input and opened Current Keyboard under Keyboard & Input Methods, changed input method from Hardware ON/OFF and then tapped the apps menu button to see running apps, then choosing WordPress to get back to it, I consistently get a force close with the following exception:
05-20 18:42:25.620 19712-19712/org.wordpress.android E/AndroidRuntime: FATAL EXCEPTION: main
                                                                       Process: org.wordpress.android, PID: 19712
                                                                       java.lang.RuntimeException: Unable to start activity ComponentInfo{org.wordpress.android/org.wordpress.android.ui.main.WPMainActivity}: java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class android.support.v7.widget.SearchView$SavedState instead. This usually happens when two views of different type have the same id in the same hierarchy. This view's id is id/menu_search. Make sure other views do not use the same id.
                                                                           at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
                                                                           at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
                                                                           at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3947)
                                                                           at android.app.ActivityThread.access$900(ActivityThread.java:151)
                                                                           at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1309)
                                                                           at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                           at android.os.Looper.loop(Looper.java:135)
                                                                           at android.app.ActivityThread.main(ActivityThread.java:5254)
                                                                           at java.lang.reflect.Method.invoke(Native Method)
                                                                           at java.lang.reflect.Method.invoke(Method.java:372)
                                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
                                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
                                                                        Caused by: java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class android.support.v7.widget.SearchView$SavedState instead. This usually happens when two views of different type have the same id in the same hierarchy. This view's id is id/menu_search. Make sure other views do not use the same id.
                                                                           at android.view.View.onRestoreInstanceState(View.java:13764)
                                                                           at android.widget.TextView.onRestoreInstanceState(TextView.java:3781)
                                                                           at android.view.View.dispatchRestoreInstanceState(View.java:13740)
                                                                           at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
                                                                           at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
                                                                           at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
                                                                           at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
                                                                           at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
                                                                           at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
                                                                           at android.view.View.restoreHierarchyState(View.java:13718)
                                                                           at android.app.Fragment.restoreViewState(Fragment.java:634)
                                                                           at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:914)
                                                                           at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1067)
                                                                           at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1049)
                                                                           at android.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1869)
                                                                           at android.app.Activity.performCreateCommon(Activity.java:5985)
                                                                           at android.app.Activity.performCreate(Activity.java:5992)
                                                                           at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
                                                                           at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
                                                                           at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387) 
                                                                           at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3947) 
                                                                           at android.app.ActivityThread.access$900(ActivityThread.java:151) 
                                                                           at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1309) 
                                                                           at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                           at android.os.Looper.loop(Looper.java:135) 
                                                                           at android.app.ActivityThread.main(ActivityThread.java:5254) 
                                                                           at java.lang.reflect.Method.invoke(Native Method) 
                                                                           at java.lang.reflect.Method.invoke(Method.java:372) 
                                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) 
                                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) 
05-20 18:42:25.621 1330-1350/system_process W/ActivityManager:   Force finishing activity 1 org.wordpress.android/.ui.main.WPMainActivity
  • Enhancement: it would be great to make the search trigger itself after some milliseconds of inactivity as opposed to having the user hit the “search” icon. It’s more intuitive, I found myself several times entering a search phrase and waiting for something to happen, then hitting the search icon to make the search actually happen. Might be just me though, but it felt awkward, so I guess the UI was going against my intuition in this sense.
  • On a real Nexus 5 with Android 6.0: just tinkering with entering / erasing / reentering words the app closed and looking in the logcat the only weird thing I could find was this (ccing it here hoping it is relevant to the issue, as I didn't find anything specific with our package names):
05-20 20:03:27.075 10346-10346/? W/ContextImpl: Calling a method in the system process without a qualified user: android.app.ContextImpl.startService:1221 android.content.ContextWrapper.startService:581 android.content.ContextWrapper.startService:581 com.android.keychain.KeyChainBroadcastReceiver.onReceive:12 android.app.ActivityThread.handleReceiver:2725 
05-20 20:03:27.078 7506-7506/? I/Auth: [AuthDelegateWrapper] Service intent: Intent { cmp=com.google.android.gms/.auth.account.authenticator.DefaultAuthDelegateService }.
05-20 20:03:27.104 7495-7495/? W/IcingConfigParser: Invalid input: icing_corpus_weights_json
                                                    android.util.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 391
                                                        at android.util.JsonReader.syntaxError(JsonReader.java:1159)
                                                        at android.util.JsonReader.checkLenient(JsonReader.java:838)
                                                        at android.util.JsonReader.nextInArray(JsonReader.java:613)
                                                        at android.util.JsonReader.peek(JsonReader.java:343)
                                                        at android.util.JsonReader.hasNext(JsonReader.java:319)
                                                        at com.google.android.apps.gsa.search.core.m.a.d.ex(IcingConfigParser.java:38)
                                                        at com.google.android.apps.gsa.extradex.searchboxroot.m.<init>(SearchboxHelper.java:156)
                                                        at com.google.android.apps.gsa.extradex.searchboxroot.o.<init>(SearchboxWorkerImpl.java:277)
                                                        at com.google.android.apps.gsa.extradex.searchboxroot.u.get(SearchboxWorkerImpl_Factory.java:1261)
                                                        at a.a.k.get(ScopedProvider.java:47)
                                                        at com.google.android.apps.gsa.extradex.searchboxroot.a.By(DaggerSearchboxWorkerComponent.java:673)
                                                        at com.google.android.apps.gsa.extradex.searchboxroot.EntryPoint.createWorker(EntryPoint.java:26)
                                                        at com.google.android.apps.gsa.search.core.service.n$1.onSuccess(ModuleWorker.java:127)
                                                        at com.google.common.util.concurrent.ag$2.run(Futures.java:1358)
                                                        at com.google.android.apps.gsa.shared.util.concurrent.a.z$1.run(TaskRunnerImpl.java:618)
                                                        at android.os.Handler.handleCallback(Handler.java:739)
                                                        at android.os.Handler.dispatchMessage(Handler.java:95)
                                                        at android.os.Looper.loop(Looper.java:148)
                                                        at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
05-20 20:03:27.108 7495-10367/? I/NativeCrashHandler: Loaded shared library: nativecrashreporter
05-20 20:03:27.128 7528-10366/? W/IcingInternalCorpora: getNumBytesRead when not calculated.

  • when searching for language-specific characters such as ñ in spanish, I’m getting matches for n too. I don’t know if this is something from the API or something with how the Android client is telling the API what it wants to be searched. Take a look at the screenshot below: I’m looking for the word paño which means tissue and I’m getting things that start with pano, like panorama. Testing a bit on the web I can see the results thrown by the es and en sites are different in this regard:

https://es.search.wordpress.com/?src=organic&q=pa%C3%B1o
https://en.search.wordpress.com/?src=organic&q=pa%C3%B1o

It seems the context in which the search is made produces different results. I have tried other words in other languages (german, english, portuguese) and I can see the results are different depending on the “scope” given (i.e., using the en.search or es.search or pt.search, for instance).

So I think this needs a bit of research and thought maybe? As I have configured my phone for Spanish but still the Wordpress mobile app is searching within the context of the English set, to name it in a way as per my tests? (i.e. the results in my english-configured phone using the WordPress Android app are the same as when I use the en.search domain on the web).

I’ll take a look at the code for sure, wondering if we should check and hunt for those Cursor problems? let me know your thoughts 👍

@nbradbury
Copy link
Contributor Author

wondering if we should check and hunt for those Cursor problems?

I wasn't able to repro those cursor problems (StaleDataException and IllegalStateException), but I'm pretty sure I know the cause.

A new cursor for the suggestions adapter is created every time you type in the search box, even if you hit backspace and there wasn't anything there. So if you rapidly backspace on an empty search box fast enough, the cursor is being rapidly recreated which could let to those exceptions.

This should be resolved in 6c572bc, which avoids changing the cursor when the filter hasn't changed.

@nbradbury
Copy link
Contributor Author

Enhancement: it would be great to make the search trigger itself after some milliseconds of inactivity as opposed to having the user hit the “search” icon.

We're actually hoping to have "live" search at a later point, which would work exactly as you describe. However, at the moment the backend really isn't fast enough to make this work well, and I think it would be more annoying than useful because of this.

@mzorz
Copy link
Contributor
mzorz commented May 23, 2016

👍

2016-05-23 12:50 GMT-03:00 Nick Bradbury notifications@github.com:

Enhancement: it would be great to make the search trigger itself after
some milliseconds of inactivity as opposed to having the user hit the
“search” icon.

We're actually hoping to have "live" search at a later point, which would
work exactly as you describe. However, at the moment the backend really
isn't fast enough to make this work well, and I think it would be more
annoying than useful because of this.


You are receiving this because you were assigned.
Reply to this email directly or view it on GitHub
#4096 (comment)

@@ -624,7 +635,7 @@ public static void addOrUpdatePosts(final ReaderTag tag, ReaderPostList posts) {
stmtPosts.bindString(16, post.getFeaturedImage());
stmtPosts.bindString(17, post.getFeaturedVideo());
stmtPosts.bindString(18, post.getPostAvatar());
stmtPosts.bindLong (19, post.timestamp);
stmtPosts.bindDouble(19, post.timestamp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why this attribute is declared as a double, being that elsewhere the timestamp can be held as a long. The only place I can think of is maybe forcing this is this one https://github.com/wordpress-mobile/WordPress-Android/blob/feature/reader-search-results/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java#L122 - is that maybe because the API is returning a value there that could grow into a double? if not, then we should probably leave/refactor everything to be treated as long, as per this https://developer.android.com/reference/java/sql/Timestamp.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why this attribute is declared as a double, being that elsewhere the timestamp can be held as a long.

So, until search came along, that timestamp column really was a timestamp, but it's only purpose has been to determine the sort order of posts.

Search results, however, include a "score" - a double which states how relevant each post is to the submitted query - and this determines how posts are sorted. To get this to work within the existing code, I changed the timestamp column to a double and store the "score" in there.

And now that I look at it, I confess to not being proud of it :)

What really should happen is for me to rename that timestamp column (and related field in the ReaderPost model) to better reflect its purpose. If that would address your (perfectly valid) complaint, I'd like to file that as a separate issue to avoid further bloating this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely! Let's file that as a separate issue then and ref it here. I have to confess I doubted of the actual value of mentioning this at all but... when you write near-to-perfect code, it's easier for the reviewer to point fingers on stuff that don't really matter - LOL

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referenced here #4119

Copy link
Contributor Author
@nbradbury nbradbury May 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but... when you write near-to-perfect code, it's easier for the reviewer to point fingers on stuff that don't really matter

Hah! Thanks for the ego boost, but after you've been here for a while you may feel otherwise ;)

Update: About my code, that is!

@mzorz
Copy link
Contributor
mzorz commented May 23, 2016

:shipit:

@mzorz mzorz merged commit b2d7c07 into feature/reader-search-master May 23, 2016
@nbradbury nbradbury deleted the feature/reader-search-results branch December 10, 2016 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants