-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
convert synchronous js api to asynchronous and remove js interface #14564
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely a positive direction with regard to handling the reality of possible delays and keeping the UI responsive, I think most APIs (ours included) should be async for this reason and this seems like a nice way to go about it. Interested to see how it develops as you work through the CI issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to your recent changes, but I don't really understand the logic of isInit(). You're storing init state on the Kotlin side, so isn't that going to break when you have more than one add-on active? If the first one correctly initialized the API, won't that allow a second add-on that doesn't do so correctly to still access the API? Couldn't this instead be enforced on the JS end?
@@ -77,6 +92,65 @@ class ReviewerServer(activity: FragmentActivity, val mediaDir: String) : AnkiSer | |||
return when (methodName) { | |||
"getSchedulingStatesWithContext" -> getSchedulingStatesWithContext() | |||
"setSchedulingStates" -> setSchedulingStates(bytes) | |||
"init" -> jsApi.init(bytes.decodeToString()).toByteArray() | |||
"ankiSearchCard" -> jsApi.ankiSearchCard(bytes.decodeToString()).toString().toByteArray() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some recommendations:
- Split the jsAPI checks into a separate function, so this function tests getScheduling/setScheduling/else: handleJsApiPost()
- Consider using a path prefix, eg ankiSearchCard -> /jsapi/searchCard. That way you can strip the prefix and just compare the last part.
- Use POST everywhere. We don't need or want caching, and it will simplify the code
- This method is currently not async because the only two existing uses don't require collection access. But since you're going to be accessing the collection, the correct way to do so would be to use withCol instead of getColUnsafe. I suggest you convert this method to suspend, and use AnkiServer's buildResponse(). That way you don't need runBlocking in each individual method, and it takes care of returning errors properly. The JS API can then withCol as necessary.
Are you handling threads correctly at the moment? ReviewerServer runs on a background thread IIRC, so you won't be able to call UI methods directly or access activity. If you switch to suspend as recommended above, then you could use withContext(Dispatchers.Main) {...} in the routines that access UI elements. |
For current session the API initiated if version and developer mail provided but it needs to change to handle for multiple addon support. Currently only JS API is being used so kotlin side init once. I will move it to JS side for more better check.
I have changed the method to suspend and used buildResponse along with withContext(Dispatchers.Main) {...}, |
979fbef
to
981dbb7
Compare
2295e10
to
864ff39
Compare
The failing tests are likely deadlocks caused by the use of runBlocking. If you wrap the unit tests in runTest { } like ' fun doNotShowOptionsMenuWhenCollectionInaccessible() = runTest {' then you can call suspend funs without needing to use runBlocking, and it may fix the issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple more minor issues:
Thanks Mani, no further suggestions from my end. |
This is ready for review. |
Interesting! I wonder if that will add to the processing overhead / latency a lot? It may not, I don't know. We definitely don't want a sneaky JS plugin to never register with the API (and thus not check compatibility) but work anyway just because a different plugin already registered... |
Then I will update this to pass developer contract for each request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, thank you Mani!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this does it for me - I like it. I understand there is a tension between a large when
/ switch statement and separating things out where some people may feel differently
But I feel in an API handler there is always a switch statement somewhere where traffic is sent to the correct logic, and as long as the logic is small (as it is here...) then having it right in the when is in my opinion best because it is easy to see where things are / how to add them --> add them right there and done...
@krmanik just wanted to give a specific thanks for working through this with me. Quite an effort when I think about the sustained attention over time, it really adds up. This should be a big improvement to the API while it's still young though as Damien noted. Hopefully this goes in, then we can finish the module management stuff then I believe I/we will have honored all your efforts here with merges + getting them released. That's my dream anyway :-). Cheers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
blocking:
- discussion on returning
-1
rather than an object searchCards
ambiguity
I know Mike's been with this for a long time, so if these have been discussed previously, mark as resolved
Follow-ups can be done later, feel free to make an issue + assign to me
@@ -26,6 +26,11 @@ import java.io.FileInputStream | |||
|
|||
class ReviewerServer(activity: FragmentActivity, val mediaDir: String) : AnkiServer(activity) { | |||
var reviewerHtml: String = "" | |||
private val jsApi = if (activity is Reviewer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementer's choice
would be cleaner to cast to an interface and call a method if the cast succeeds
ankiSearchCard: "searchCard", | ||
ankiSearchCardWithCallback: "searchCardWithCallback", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should rethink the names here:
searchCard
opens the browsersearchCardWithCallback
performs a search and returns results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During implementation of api, only searchCard are implemented but later user wanted result in reviewer also, so used call back, poor naming choice, it can be solved in separate PR, with better naming, because we are upgrading api, so it can also be upgraded.
I have updated some review but going to create issue and assigning to you for further refactor and improvement of this. |
Happy with that. A few tests are failing I'll want to give this a second look through once CI is green, but assume it'll be approved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question: global variables in a request pipeline is a concern
Used data class for parsing developer contract so now there is no global variables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks so much!!!!
@mikehardy @krmanik Acceptable to squash? I'm happy, but want to confirm due to the size |
It is okay to squash into one commit. |
Thanks so much! |
Fantastic! |
Hi there @krmanik! This is the OpenCollective Notice for PRs merged from 2023-12-01 through 2023-12-31 If you are interested in compensation for this work, the process with details is here: https://github.com/ankidroid/Anki-Android/wiki/OpenCollective-Payment-Process#how-to-get-paid We only post one comment per person per month to avoid spamming you, regardless of the number of PRs merged, but this note applies to all PRs merged for this month Please understand that our monthly budget is never guaranteed to cover all claims - the cap on payments-per-person may be lower, but we try to make our process as fair and transparent as possible, we just need your understanding. Thanks! |
Purpose / Description
Fixes
Approach
The JavaScript interface is removed. The idea for asynchronous implementation is inspired by this commit (aa13aebc8eb6f86). A JavaScript file containing all the JS API implementations is created in the assets directory. The
fetch
API is used to send HTTP (GET/POST) requests, and incoming requests are handled on the Kotlin side of the application.How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration (SDK version(s), emulator or physical, etc)
AnkiDroid JS API Test.zip
Example test, for front field in deck
Click to see more detailed example
Learning (optional, can help others)
Describe the research stage
Links to blog posts, patterns, libraries or addons used to solve this problem
Checklist
Please, go through these checks before submitting the PR.