-
Notifications
You must be signed in to change notification settings - Fork 535
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
Fix #5: GAE Abstraction part 1 - Topic Page handler [Blocked: #85] #78
Conversation
High-level notes: - Data sources are now being referred to as 'DataProviders' - This commit also fixes the build which was broken in a previous PR ('compile' needs to be used for the proto library) - The data source module is referred to as just 'data' - This introduces a few style changes to prohibit wildcard imports - This also normalizes the JDK versions needed across modules, potentially fixing some of the repeated reversions the IDE does for a Java SDK version setting Functional differences: the app now properly shows the correct string upon initial open ("Welcome to Oppia"), and reopening thereafter correctly shows the updated string ("Welcome back to Oppia"). Rotating the device is currently broken without Dagger integration. Overall, this moves all general data provider functionality into the new module, and introduces a PersistentCacheStore to store proto messages on-disk, or load them into memory if they are available. Overall, this commit is introducing a potentially reasonable medium-term solution for data providers and on-disk proto storage. A long-term design should still be determined, but this seems like a good place to start. Tests are currently broken or missing. New TODOs will also be resolved in a later commit.
@BenHenning PTAL. This PR should help @veena14cs in topic-index-handler as well as I will use this branch for my future PRs for other APIs. |
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.
Thanks @rt4914! I did a full review, though I'm expecting the tests to change some per my comment about testing via public APIs.
Generally, the PR looks really good! I think one more thorough pass will be sufficient to get this checked in since I'm especially interested in seeing tests that we can use as canonical examples for all future HTTP test code. Please let me know if you have any questions, concerns, or doubts about any of my comments or the PR.
/** Constant values for data module will be defined here */ | ||
object Constants { | ||
|
||
const val RESPONSE_SUCCESS = 200 |
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.
Unless this is going to be processed outside of this file, we could just add it as a module-level constant to the interceptor.
Please also add a Javadoc to it, or name it something self documenting like HTTP_OK.
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.
Unable to understand your first line. Do you mean I should declare this constant in gradle.properties ?
Also, I have renamed RESPONSE_SUCCESS oto HTTP_OK
* @param rawJson: This is the string that we get in body of our response | ||
* @return String: rawJson without XSSI_PREFIX | ||
*/ | ||
fun removeXSSIPrefixFromResponse(rawJson: String): String { |
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.
Since this is a helper method, let's make it private since it shouldn't be accessed outside of this class.
When testing the interceptor, we should only test it from its public API to have parity with how other classes will interact with it. In this case, we should use intercept() to test that the XSSI prefix is properly removed.
Is there a way to use Retrofit in tests? https://riggaroo.co.za/retrofit-2-mocking-http-responses/ suggests that this is possible.
This is actually a great opportunity to demonstrate how to mock HTTP responses in Retrofit, since that will be the foundation for our later downstream fakes that we use to support the HTTP side of the entire app infrastructure for UI tests.
|
||
/** | ||
* Interceptor on top of Retrofit to modify requests and response | ||
* The Interceptor intercepts requests and response and in every response |
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.
Please wrap Javadoc comments to the 120 character limit, or put new lines between Javadoc paragraphs.
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.
@BenHenning PTAL.
.client(client.build()) | ||
.build() | ||
} | ||
return@lazy retrofit |
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.
Out of curiosity, do we actually need to use return@lazy
here? If so, why? I haven't needed that yet for lazy initialization. Do you know what it does?
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.
Actually, in my case it was giving error if I use return retrofit
and the solution for that was this.
private var retrofit: Retrofit? = null | ||
|
||
val retrofitInstance: Retrofit? by lazy { | ||
if (retrofit == null) { |
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.
Is this additional logic needed? Can't we just initialize Retrofit lazily? I don't completely follow why we need this dual lazy logic.
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.
Check the updated code for this.
|
||
/** | ||
* Model for serialization/deserialization using Moshi with Retrofit | ||
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a> |
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.
Two things regarding these links:
- Since Kotlin uses markdown for Javadocs, you can make this a bit shorter by avoiding the href anchor tag
- You can shorten these blob links by just using the first 6 hash characters, e.g. https://github.com/oppia/oppia/blob/b33aa9/core/domain/story_domain.py#L1118.
Ditto elsewhere in this PR.
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 tried this
@see [link](https://github.com/oppia/oppia/blob/b33aa9/core/domain/story_domain.py#L1118)
, but it was not working. Can you guide me this? - Changed blob links to 6 hash characters only.
import com.squareup.moshi.JsonClass | ||
|
||
/** | ||
* Model for serialization/deserialization using Moshi with Retrofit |
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.
Let's describe what this model actually is, rather than how it should be used. Ditto elsewhere in this PR.
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.
Changed the description for all models
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a> | ||
*/ | ||
@JsonClass(generateAdapter = true) | ||
data class StorySummary( |
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 believe at some point we discussed suffixing all of these GAE models with 'Gae' to help avoid conflicts with future domain models that these will be converted through. Is that still the case? If so, let's go ahead and make that change to these model classes.
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.
Added Gae
prefix to all models
@Json(name = "topic_name") val topicName: String?, | ||
@Json(name = "canonical_story_dicts") val canonicalStoryDicts: List<StorySummary?>?, | ||
@Json(name = "additional_story_dicts") val additionalStoryDicts: List<StorySummary?>?, | ||
/** skill_descriptions map has skill id as key and skill name as value */ |
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.
Consider phrasing this instead as something like:
/** A map of skill descriptions keyed by skill ID. */
That way the documentation is describing the property, and still includes the important bit about how the map is keyed & what it contains.
Ditto for degreesOfMastery
below.
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.
Done.
class NetworkInterceptorTest { | ||
|
||
@Test | ||
fun testNetworkInterceptor_removeXSSIPrefixFromResponse_withXSSIPREFIX() { |
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.
Nit on naming: when camel casing with fully capitalized names, you can lowercase all the letters after the first one (see https://google.github.io/styleguide/javaguide.html#s5.3-camel-case). In this case:
testNetworkInterceptor_removeXssiPrefixFromResponse_withXssiPrefix
Also, test names can be clearer when phrased as:
testAction_withOneCondition_andOtherCondition_hasExpectedOutcome
Your name is close to this, but consider the alternative:
testNetworkInterceptor_withXssiPrefix_removesXssiPrefix
If all tests are phrased this way, it will be really easy to see which behaviors are being tested which can help identify missing cases for more complex classes.
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.
@BenHenning Thanks a lot. This makes sense.
This is loosely based on both https://github.com/tfcporciuncula/dagger-simple-way and https://dagger.dev/android.html, except this approach is meant to keep activities and fragments as thin as possible and push as much UI presentation and service business logic into Dagger-provided objects as possible. This maximizes Dagger use throughout the codebase, and opens the potential for future code generation for these thin adapter fragments & services. This fixes the rotation bug in the app: rotating upon the initial app open retains the 'Welcome to Oppia' label since the same user app history controller is used in both instances of HomeFragment due to it being singleton scoped and not needing to be recreated. No tests were updated with these changes.
…e-abstraction-base
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.
@BenHenning I have replied to most of your comments and made changes accordingly. I will now work on Retrofit Test cases
/** Constant values for data module will be defined here */ | ||
object Constants { | ||
|
||
const val RESPONSE_SUCCESS = 200 |
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.
Unable to understand your first line. Do you mean I should declare this constant in gradle.properties ?
Also, I have renamed RESPONSE_SUCCESS oto HTTP_OK
|
||
/** | ||
* Interceptor on top of Retrofit to modify requests and response | ||
* The Interceptor intercepts requests and response and in every response |
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.
@BenHenning PTAL.
object NetworkSettings { | ||
|
||
// TODO(#74): Move this to DI graph | ||
const val BASE_URL = "https://oppia.org" |
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 am multiple doubts in this.
- DEVELOPER_URL similar to demo app? I haven't seen that anywhere.
- For switching I am introduing a boolean value which will switch between developer and prod server.
object NetworkSettings { | ||
|
||
// TODO(#74): Move this to DI graph | ||
const val BASE_URL = "https://oppia.org" |
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 have removed BASE_URL and intoduced a fun getBaseUrl()
and using boolean value this method switches between the server.
object NetworkSettings { | ||
|
||
// TODO(#74): Move this to DI graph | ||
const val BASE_URL = "https://oppia.org" |
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.
Also, not sure what should be the exact value of PROD and DEVELOPER URL for now.
|
||
/** | ||
* Model for serialization/deserialization using Moshi with Retrofit | ||
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a> |
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 tried this
@see [link](https://github.com/oppia/oppia/blob/b33aa9/core/domain/story_domain.py#L1118)
, but it was not working. Can you guide me this? - Changed blob links to 6 hash characters only.
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a> | ||
*/ | ||
@JsonClass(generateAdapter = true) | ||
data class StorySummary( |
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.
Added Gae
prefix to all models
|
||
/** | ||
* Model for serialization/deserialization using Moshi with Retrofit * | ||
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/controllers/topic_viewer.py#L45">Oppia-Web Topic structure</a> |
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 it is pointing correctly because the response is a mixture of multiple responses from different function calls.
@Json(name = "topic_name") val topicName: String?, | ||
@Json(name = "canonical_story_dicts") val canonicalStoryDicts: List<StorySummary?>?, | ||
@Json(name = "additional_story_dicts") val additionalStoryDicts: List<StorySummary?>?, | ||
/** skill_descriptions map has skill id as key and skill name as value */ |
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.
Done.
class NetworkInterceptorTest { | ||
|
||
@Test | ||
fun testNetworkInterceptor_removeXSSIPrefixFromResponse_withXSSIPREFIX() { |
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.
@BenHenning Thanks a lot. This makes sense.
@BenHenning PTAL |
NB: This is blocked on #85. |
* introduced topic index handler * Call retrofit data handlers from data module * Update topic_index_items.xml * removed extra space * fixed issues * Update AndroidManifest.xml * Removed Ui part. http handlers are included in data module * done changes in handler and data as per the specs * Update TopicSummarytDicts.kt * Update NetworkSettings.kt * added classroom datahandler * renamed corresponding classes to classroom * fixed issues * Update GaeClassroom.kt * removed spaces * Update AndroidManifest.xml * removed unwanted spaces * added space
…-android into gae-abstraction-base
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.
@BenHenning PTAL
private var retrofit: Retrofit? = null | ||
|
||
@Before | ||
@Throws(Exception::class) |
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 did search this on internet but haven't found a proper answer for this. I have seen different resources which uses this and there are some resources which do not use this.
Even I am not sure, whether we actually need this or not.
|
||
/** Constant which defines successful API call */ | ||
const val HTTP_OK = 200 | ||
|
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.
@BenHenning Not sure how to dagger-ify this.
data/src/main/java/org/oppia/data/backends/gae/NetworkModule.kt
Outdated
Show resolved
Hide resolved
data/src/test/java/org/oppia/data/backends/test/NetworkInterceptorTest.kt
Show resolved
Hide resolved
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.
Generally LGTM. Please re-assign to me if you'd like me to do another pass, but as long as you address my comments feel free to merge this.
import javax.inject.Singleton | ||
|
||
/** | ||
* Sample resource https://github.com/gahfy/Feed-Me/tree/unitTests |
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.
Nit: let's just make this a separate paragraph in the below Javadoc. Currently, this is a floating Javadoc with no associated element. :)
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.
Actually, I'm not sure where this Javadoc should go since this module isn't related to testing directly. Maybe nowhere for now?
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 have shifted to this javadoc because this code has been referenced from that link and therefore it is worth mentioning it here for now. And later we can remove this.
data/src/main/java/org/oppia/data/backends/gae/NetworkModule.kt
Outdated
Show resolved
Hide resolved
data/src/main/java/org/oppia/data/backends/gae/model/GaeTopicSummarytDict.kt
Outdated
Show resolved
Hide resolved
data/src/test/java/org/oppia/data/backends/test/MockTopicTest.kt
Outdated
Show resolved
Hide resolved
|
||
@Test | ||
@Throws(Exception::class) | ||
fun testMockTopicService_withTopicName_success() { |
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.
Per the name, this doesn't seem to include the actual action that's being tested. Can you update it to include the operation itself?
Also, it's often more useful to be specific about what's successful (e.g. that the response has the expected topic name). That way we can easily check multiple tests with a similar naming style for thoroughness of behaviors being tested.
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.
Changed this name to testTopicService_usingFakeJson_deserializationSuccessful
but still not convinced. I am finding it difficult to name the methods correctly. Will need to see multiple code samples to get proper understanding.
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 seems reasonable, though you could probably make it slightly clearer by including what is successful, e.g.:
testService_withFakeJson_deserializesCorrectTopicName
data/src/test/java/org/oppia/data/backends/test/NetworkInterceptorTest.kt
Show resolved
Hide resolved
@BenHenning PTAL |
data/src/test/java/org/oppia/data/backends/test/NetworkInterceptorTest.kt
Show resolved
Hide resolved
|
||
@Test | ||
@Throws(Exception::class) | ||
fun testMockTopicService_withTopicName_success() { |
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 seems reasonable, though you could probably make it slightly clearer by including what is successful, e.g.:
testService_withFakeJson_deserializesCorrectTopicName
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.
Thanks @rt4914. The recent changes LGTM; left a few comments to improve clarity on a few things.
This PR is complete but it does need some more test cases to be introduced which will be finished with other PRs related to services. |
Explanation
The PR contains data module, necessary libraries for API calls and Topic Management System models, retrofit instance and interceptor.
Also this PR was earlier on #61 and #75
Libraries Used
This library is responsible for making API calls to Oppia backend
This library is the actual deserializer that we are using over GSON.
Moshi Kotlin support library for using Moshi in Kotlin.
Checklist
Work items: