Skip to content

Tenor API integrated with GIF Picker#1

Merged
ThomazFB merged 30 commits intoissue/11456-add-tenor-supportfrom
feature/insert-tenor-api
Mar 26, 2020
Merged

Tenor API integrated with GIF Picker#1
ThomazFB merged 30 commits intoissue/11456-add-tenor-supportfrom
feature/insert-tenor-api

Conversation

@ThomazFB
Copy link
Copy Markdown
Owner

@ThomazFB ThomazFB commented Mar 25, 2020

Summary

This Pull Request brings integration for the Tenor API within the pre-existent GIF selection code inside the app. As described at issue 11456.

screencapture-1585095917110 2020-03-24 23_04_23

The solution proposes the following implementation:

  • A GifProvider interface must be the only coupling between all code for the GIF picker and the Tenor implementation, avoiding any reference to the Tenor library and making it easy to replace the Provider as we're doing now

  • The TenorProvider implements the GifProvider interface and is injected within the DataSource via Dagger, this way only the AppModule knows about the TenorProvider. Also, the TenorProvider is covered by unit tests in the TenorProviderTest class.

  • The already implemented pagination feature and list actions are all kept and integrated to work smoothly with the GifProvider, this integration happens through the available PositionalDataSource, The Tenor implementation makes sure that positioning tracking is respected

  • The original integration with the MediaBrowserActivity was restored, making GIF selection available again

Known missing points for upcoming Pull Requests

  • Integration with EditPostActivity not restored yet
  • All Giphy packages and class naming is still missing, this will be the last modification to not storm the code review with name changes within the Pull Request
  • Change Giphy icons
  • Analytics support still to be added
  • Unit tests for PickerDataSource

Testing

  1. Create a Tenor API key and paste it associated with the wp.tenor.api_key as the value inside your gradle.properties
  2. Go to the Media Browser
  3. Click on the Add button at the top right. Select the Choose from Tenor option
  4. Type in the search bar to query GIFs
  5. Once inside the Gif Picker, make sure that selection, preview and add are all working as expected

PR submission checklist

  • I have considered adding unit tests where possible.
  • I have considered adding accessibility improvements for my changes.
  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

ThomazFB added 21 commits March 20, 2020 21:33
@ThomazFB ThomazFB changed the title Tenor API integrated with GIF Picker #1 Tenor API integrated with GIF Picker Mar 25, 2020
Copy link
Copy Markdown

@planarvoid planarvoid left a comment

Choose a reason for hiding this comment

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

thanks for the changes! Overall it looks really good 👍 I have some comments, feel free to let me know what you think.

I've encountered a crash when searching for the string "isengard" (don't ask me why):

java.lang.IllegalArgumentException: PositionalDataSource requires initial load size to be a multiple of page size to support internal tiling. loadSize 3, position 0, totalCount 4, pageSize 21
        at androidx.paging.PositionalDataSource$LoadInitialCallbackImpl.onResult(PositionalDataSource.java:223)
        at org.wordpress.android.viewmodel.giphy.GiphyPickerDataSource$requestInitialSearch$1.invoke(GiphyPickerDataSource.kt:91)
        at org.wordpress.android.viewmodel.giphy.GiphyPickerDataSource$requestInitialSearch$1.invoke(GiphyPickerDataSource.kt:14)
        at org.wordpress.android.viewmodel.giphy.provider.TenorProvider$search$$inlined$simpleSearch$1.success(TenorProvider.kt:195)
        at org.wordpress.android.viewmodel.giphy.provider.TenorProvider$search$$inlined$simpleSearch$1.success(TenorProvider.kt:104)
        at com.tenor.android.core.response.WeakRefCallback.success(WeakRefCallback.java:184)
        at com.tenor.android.core.response.WeakRefCallback.access$100(WeakRefCallback.java:30)
        at com.tenor.android.core.response.WeakRefCallback$1.run(WeakRefCallback.java:88)
        at com.tenor.android.core.weakref.WeakRefRunnable.run(WeakRefRunnable.java:34)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

import com.tenor.android.core.model.impl.Result
import org.wordpress.android.viewmodel.giphy.MutableGiphyMediaViewModel

class TenorProviderTestUtils {
Copy link
Copy Markdown

@planarvoid planarvoid Mar 25, 2020

Choose a reason for hiding this comment

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

I think it would be cleaner to make this an object and remove the companion object.
And personally I'd call this class Fixtures even though I see there is no Fixture class yet in the WPAndroid project.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I liked a lot your suggestion for Fixture nomenclature, it makes a lot more sense. Maybe we could suggest starting inserting this kind of unit test code from now on? I've committed the change in 61e7bca. What do you think?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sounds good, thanks for the change

verify(gifSearchCall, times(1)).enqueue(callbackCaptor.capture())
val capturedCallback = callbackCaptor.value
capturedCallback.success(ApplicationProvider.getApplicationContext(), gifResponse)
assertTrue("onSuccess should be called", onSuccessWasCalled)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I usually prefer usage of assertThat(onSuccessWasCalled).isTrue() from the org.assertj.core.api.Assertions.assertThat package. This is not very important but it tends to be more readable if you have more than one assertion.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Awesome suggestion, it really improves the assertion readability, it's done! Changed in 11fde9b.

* the init will use the default implementation provided by the Tenor library.
*/
init {
ApiService.Builder(context, IApiClient::class.java).apply {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I was wondering about initializing the api client in the init method. I was thinking that maybe it might be nicer to either do it in the ApplicationModule and directly inject the proper ApiClient or if this doesn't work, use the laze initialization on the apiClient by lazy {}.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Nice! Makes a lot of sense, I was intentionally trying to keep the API configuration inside the Provider, but your suggestion to initializing it inside the ApplicationModule created an opening for a more concise dependency construction inside the TenorProvider. Thanks! The change is committed at 0b7035d.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the change! I think both options (application module and kotlin lazy load) would be fine, it just felt that the init is somehow between these two approaches 👍


internal class TenorProvider @JvmOverloads constructor(
val context: Context,
tenorClient: IApiClient? = null
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this field planned to be injected? Or could it be removed?

Copy link
Copy Markdown
Owner Author

@ThomazFB ThomazFB Mar 25, 2020

Choose a reason for hiding this comment

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

This field was created to make an easy opening for unit tests but allowing it as an optional value without disregard the API initialization. I think the change at 0b7035d already solved this point since the initialization of the API is now being made outside the TenorProvider, at the ApplicationModule, and the injection of the IApiClient happens right there after the API key configuration.

What do you think?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

good point about the unit tests, I like the way it's done now

/**
* To better refers to the Tenor API maximum GIF limit per request
*/
private val maximumAllowedLoadSize = 50
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just for consistency, I'd move this to the companion object and use snake case.

Copy link
Copy Markdown
Owner Author

@ThomazFB ThomazFB Mar 25, 2020

Choose a reason for hiding this comment

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

Done! Changed in 6ee5513

.setEnablePlaceholders(false)
.setPrefetchDistance(15)
.setInitialLoadSizeHint(42)
.setPageSize(21)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe pull out all of these constants to a companion object?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Done! Changed in e5eef80.

@ThomazFB
Copy link
Copy Markdown
Owner Author

ThomazFB commented Mar 25, 2020

thanks for the changes! Overall it looks really good 👍 I have some comments, feel free to let me know what you think.

I've encountered a crash when searching for the string "isengard" (don't ask me why):

java.lang.IllegalArgumentException: PositionalDataSource requires initial load size to be a multiple of page size to support internal tiling. loadSize 3, position 0, totalCount 4, pageSize 21
        at androidx.paging.PositionalDataSource$LoadInitialCallbackImpl.onResult(PositionalDataSource.java:223)
        at org.wordpress.android.viewmodel.giphy.GiphyPickerDataSource$requestInitialSearch$1.invoke(GiphyPickerDataSource.kt:91)
        at org.wordpress.android.viewmodel.giphy.GiphyPickerDataSource$requestInitialSearch$1.invoke(GiphyPickerDataSource.kt:14)
        at org.wordpress.android.viewmodel.giphy.provider.TenorProvider$search$$inlined$simpleSearch$1.success(TenorProvider.kt:195)
        at org.wordpress.android.viewmodel.giphy.provider.TenorProvider$search$$inlined$simpleSearch$1.success(TenorProvider.kt:104)
        at com.tenor.android.core.response.WeakRefCallback.success(WeakRefCallback.java:184)
        at com.tenor.android.core.response.WeakRefCallback.access$100(WeakRefCallback.java:30)
        at com.tenor.android.core.response.WeakRefCallback$1.run(WeakRefCallback.java:88)
        at com.tenor.android.core.weakref.WeakRefRunnable.run(WeakRefRunnable.java:34)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

Hi @planarvoid! Thank you for your review! I replied to every conversation with the proposed fix!

About this crash, it was a desync between the position track returned by Tenor and our PositionalDataSource when a small list of GIFs is returned for a given query, so the reason why "isengard" was crashing the app is that this query only returns 3 GIFs, triggering this bug. With that said, I made the DataSource more resilient to this kind of scenario. The change is committed in f7ec187.

Copy link
Copy Markdown

@planarvoid planarvoid left a comment

Choose a reason for hiding this comment

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

Thanks for the changes and for the fix! The code now looks great and the feature works well 👍. I cannot merge this branch but feel free to merge it into the feature branch.

@ThomazFB ThomazFB merged commit 78106a9 into issue/11456-add-tenor-support Mar 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants